package com.webchat.chat.core.tools;

import com.corundumstudio.socketio.BroadcastOperations;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.webchat.chat.core.dto.evenMessage.MsgInfo;
import com.webchat.chat.core.enums.ServiceStatus;
import com.webchat.chat.core.enums.SocketClientType;
import com.webchat.chat.core.enums.SocketRoomStatus;
import com.webchat.chat.core.enums.UserOnlineStatus;
import com.webchat.chat.core.redis.RedisService;
import com.webchat.chat.model.*;
import com.webchat.chat.service.*;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by Owner on 2018/6/27.
 */
public class SocketUtil {

    /**
     * Protect from being instance.
     */
    private SocketUtil() {
    }

    /**
     * 查找socket的client
     *
     * @return
     */
    public static SocketIOClient getSocketIOClient(SocketClient redisSocketClient, SocketIOServer socketIOServer) {
        if (redisSocketClient == null) {
            return null;
        }
        Long mostSignificantBits = redisSocketClient.getMostSignificantBits();
        Long leastSignificantBits = redisSocketClient.getLeastSignificantBits();
        if (mostSignificantBits == null || leastSignificantBits == null) {
            return null;
        }
        UUID uuid = new UUID(mostSignificantBits, leastSignificantBits);
        return socketIOServer.getClient(uuid);
    }

    /**
     * 查找客户的房间
     */
    public static Set<String> getClientRooms(SocketIOClient client) {
        if (client == null) {
            return null;
        }
        return client.getAllRooms().stream().filter(roomId -> StringUtils.isNotBlank(roomId)).collect(Collectors.toSet());
    }

    /**
     * 查找客服的房间
     */
    public static Set<String> getClientRooms(SocketClient redisSocketClient, SocketIOServer socketIOServer) {
        if (redisSocketClient == null) {
            return null;
        }
        SocketIOClient socketIOClient = getSocketIOClient(redisSocketClient, socketIOServer);
        if (socketIOClient == null) {
            return null;
        }
        return getClientRooms(socketIOClient);
    }

    /**
     * 根据socketclient获取User-Agent信息
     */
    public static String getUserAgentInfo(SocketIOClient client) {
        return client.getHandshakeData().getHttpHeaders().get("User-Agent");
    }

    /**
     * 根据socketclient获取参数-租户代号
     */
    public static String getTenantCode(SocketIOClient client) {
        return client.getHandshakeData().getSingleUrlParam(SocketConstant.TENANT_CODE);
    }

    /**
     * 根据socketclient获取参数-用户id
     */
    public static String getUserId(SocketIOClient client) {
        return client.getHandshakeData().getSingleUrlParam(SocketConstant.CONNECT_ID);
    }

    /**
     * 根据socketclient获取参数-获取用户名
     */
    public static String getUserName(SocketIOClient client) {
        return client.getHandshakeData().getSingleUrlParam(SocketConstant.CUSTOMER_NAME);
//        String socketClientTypeStr = client.getHandshakeData().getSingleUrlParam(SocketConstant.SOCKET_CLIENT_TYPE);
//        if (StringUtils.isBlank(socketClientTypeStr)) {
//            socketClientTypeStr = "客户";
//        }
//        SocketClientType socketClientType = SocketClientType.valueOf(socketClientTypeStr);
//        if (Objects.equals(SocketClientType.客户, socketClientType)) {
//            return client.getHandshakeData().getSingleUrlParam(SocketConstant.CONNECT_ID);
//        } else {
//            return client.getHandshakeData().getSingleUrlParam(SocketConstant.CLIENT_ID);
//        }
    }

    /**
     * 根据socketclient获取参数-获取通信密码(客户通信密码是无效的)
     */
    public static String getConnectPwd(SocketIOClient client) {
        return client.getHandshakeData().getSingleUrlParam(SocketConstant.PWD_CONNECT);
    }

    /**
     * 根据socketclient获取参数-获取客户端类型
     */
    public static SocketClientType getSocketClientType(SocketIOClient client) {
        String type = client.getHandshakeData().getSingleUrlParam(SocketConstant.SOCKET_CLIENT_TYPE);

        if (StringUtils.isBlank(type)) {
            type = "客户";
        }
        return SocketClientType.valueOf(type);

    }

    /**
     * 根据socketclient获取参数-获取clientID
     */
    public static String getClientId(SocketIOClient client) {
        return client.getHandshakeData().getSingleUrlParam(SocketConstant.CLIENT_ID);
    }

    /**
     * 客户端连接
     */
    public static void clientConnect(SocketIOClient client, String clientId, String tenantCode, String userName, String ua,
                                     RedisService redisService, SocketClientType socketClientType,
                                     RoomClientService roomClientService, SocketClientService socketClientService,
                                     SocketIOServer socketIOServer,String customerId,UserService userService) {
        User user = userService.findById(customerId);
        if (null!= user){
            user.setOnlineStatus(UserOnlineStatus.在线);
            userService.update(user);
        }

        Date now = new Date();
        SocketClient socketClient = getRedisSocketClient(socketClientType, tenantCode, customerId, redisService);
        if (socketClient == null) {
            socketClient = new SocketClient();
            socketClient.setClientId(clientId);
            socketClient.setLeastSignificantBits(client.getSessionId().getLeastSignificantBits());
            socketClient.setMostSignificantBits(client.getSessionId().getMostSignificantBits());
            socketClient.setTenantCode(tenantCode);
            socketClient.setType(socketClientType);
            socketClient.setConnected(Boolean.TRUE);
            socketClient.setLastConnectTime(now);
            socketClient.setAduit(false);
            socketClient.setServiceStatus(ServiceStatus.正常);
            socketClient.setUserName(userName);
            socketClient.setUserAgent(ua);
            socketClient.setCreatedBy(ChatConstant.SYS_OPRATION_NAME);
            socketClient.setCreatedDate(now);
        } else {
            socketClient.setMostSignificantBits(client.getSessionId().getMostSignificantBits());
            socketClient.setLeastSignificantBits(client.getSessionId().getLeastSignificantBits());
            socketClient.setConnected(Boolean.TRUE);
            socketClient.setLastConnectTime(now);
            socketClient.setServiceStatus(ServiceStatus.正常);
            socketClient.setUserAgent(ua);
            socketClient.setLastModifiedBy(ChatConstant.SYS_OPRATION_NAME);
            socketClient.setLastModifiedDate(now);
        }
        socketClient.setConnectCustomerNum(0);

        String userId = SocketUtil.getUserId(client);
        socketClient.setUserId(userId);
//        if(Objects.equals(socketClientType, SocketClientType.客服)){
//            socketClient.setUserId(userId);
//        }else{
//            socketClient.setUserId("none");
//        }
        // 保存SocketIOClient至redis，设置过期时间（时间可以远大于会话失效时间），方便其他地方使用此客户端给对应用户发送信息。
        // 例如：会话失效时，发送消息给对应用户，提醒失效。
        // 例如：当账号被另一人挤下线之后给之前用户发送掉线信息；
        // 上述功能已在下面方法完善
        saveClient2Redis(socketClient, client, socketClientType, tenantCode, customerId, redisService, Boolean.TRUE);
        connectSocketClientSave2DB(client, clientId, tenantCode, userName, ua, socketClientType, socketClientService);

    }

    /**
     * 客户端断开连接
     */
    public static void clientDisconnect(String clientId, String tenantCode, RedisService redisService,
                                        SocketClientType socketClientType, SocketClientService socketClientService,
                                        SocketIOClient client, SocketIOServer socketIOServer, RoomClientService roomClientService,
                                        SocketRoomService socketRoomService,String customerId,UserService userService) {
        User user = userService.findById(customerId);
        if (null != user) {
            user.setOnlineStatus(UserOnlineStatus.离线);
            userService.update(user);
        }

        Date now = new Date();
        SocketClient socketClient = socketClientService.findSocketClientByClientId(clientId);
        if (socketClient == null) {
            //理论上不可能为空
            return;
        }
        socketClient.setLastModifiedDate(now);
        socketClient.setLastModifiedBy(ChatConstant.SYS_OPRATION_NAME);
        socketClient.setConnected(Boolean.FALSE);
        socketClient.setServiceStatus(ServiceStatus.离开);
        socketClient.setConnectCustomerNum(0);
        SocketUtil.saveClient2Redis(socketClient, null, socketClientType, tenantCode, customerId, redisService, Boolean.FALSE);
        socketClientService.update(socketClient);

        //客户端离开房间
        Set<String> allRooms = client.getAllRooms();
        allRooms.forEach(r -> {
            BroadcastOperations roomOperations = socketIOServer.getRoomOperations(r);
            if (roomOperations != null) {
                SocketEventUtil.customerLeave(roomOperations, socketClient.getUserName(), socketClient.getUserName());
                client.leaveRoom(r);
            }
        });

        if (Objects.equals(SocketClientType.客户, socketClientType)) {
            SocketUtil.removeWaitCustomer(tenantCode, customerId, redisService);
        }else{

        }

    }

    /**
     * 连接时保存连接信息至DB
     *
     * @param client
     * @param clientId
     * @param tenantCode
     * @param userName
     * @param ua
     * @param socketClientType
     * @param socketClientService
     */
    private static void connectSocketClientSave2DB(SocketIOClient client, String clientId, String tenantCode,
                                                   String userName, String ua, SocketClientType socketClientType,
                                                   SocketClientService socketClientService) {
        Date now = new Date();
        String userId = SocketUtil.getUserId(client);
        SocketClient socketClient = socketClientService.findSocketClientByClientId(clientId);

        if (socketClient == null) {
            socketClient = new SocketClient();
            socketClient.setClientId(clientId);
            socketClient.setLeastSignificantBits(client.getSessionId().getLeastSignificantBits());
            socketClient.setMostSignificantBits(client.getSessionId().getMostSignificantBits());
            socketClient.setTenantCode(tenantCode);
            socketClient.setType(socketClientType);
            socketClient.setConnected(Boolean.TRUE);
            socketClient.setLastConnectTime(now);
            socketClient.setServiceStatus(ServiceStatus.正常);
            socketClient.setUserName(userName);
            socketClient.setUserAgent(ua);
            socketClient.setCreatedBy(ChatConstant.SYS_OPRATION_NAME);
            socketClient.setCreatedDate(now);
            socketClient.setUserId(userId);
            socketClientService.save(socketClient);
        } else {
            socketClient.setLeastSignificantBits(client.getSessionId().getLeastSignificantBits());
            socketClient.setMostSignificantBits(client.getSessionId().getMostSignificantBits());
            socketClient.setConnected(Boolean.TRUE);
            socketClient.setLastConnectTime(now);
            socketClient.setUserId(userId);
            socketClient.setUserName(userName);
            socketClient.setServiceStatus(ServiceStatus.正常);
            socketClient.setUserAgent(ua);
            socketClient.setLastModifiedBy(ChatConstant.SYS_OPRATION_NAME);
            socketClient.setLastModifiedDate(now);
            socketClientService.update(socketClient);
        }
    }

    /**
     * 获取redis中的客户端信息
     *
     * @param clientType
     * @param tenantCode
     * @param customerId
     * @param redisService
     * @return
     */
    public static SocketClient getRedisSocketClient(SocketClientType clientType, String tenantCode, String customerId, RedisService redisService) {
        if (Objects.equals(clientType, SocketClientType.客服)) {
            return (SocketClient) redisService.getMapField(RedisConstant.SCOKET_CLIENT_CS + tenantCode, customerId);
        } else {
            return (SocketClient) redisService.getMapField(RedisConstant.SCOKET_CLIENT_C + tenantCode, customerId);
        }
    }

    /**
     * 保存客户端信息至redis
     * @param client 自定义客户端信息
     * @param socketIOClient socketIO客户端信息
     * @param clientType 客户端类型
     * @param tenantCode 租户代号
     * @param userId
     * @param redisService
     * @param isAdd 是否添加。true-新增；false-删除；null-不做操作
     */
    public static void saveClient2Redis(SocketClient client, SocketIOClient socketIOClient, SocketClientType clientType, String tenantCode,
                                        String userId, RedisService redisService, Boolean isAdd) {
        if(isAdd != null){
            if(isAdd){
                //redisService.addMap(RedisConstant.SCOKETIO_CLIENT + tenantCode, clientId, socketIOClient);
            }else{
                //redisService.removeMapField(RedisConstant.SCOKETIO_CLIENT + tenantCode, clientId);
            }
        }

        if (Objects.equals(clientType, SocketClientType.客服)) {
            redisService.addMap(RedisConstant.SCOKET_CLIENT_CS + tenantCode, userId, client);
        } else {
            redisService.addMap(RedisConstant.SCOKET_CLIENT_C + tenantCode, userId, client);
        }
    }

    /**
     * 保存客户端信息至等待队列
     *
     * @param tenantCode
     * @param clientId
     * @param redisService
     * @return
     */
    public static void saveWaitClient2Redis(SocketClient client, String tenantCode, String clientId, RedisService redisService) {
        redisService.addMap(RedisConstant.SCOKET_CLIENT_WAIT_C + tenantCode, clientId, client);
    }

    /**
     * 获取等待的用户
     *
     * @param tenantCode
     * @param clientId
     * @param redisService
     * @return
     */
    public static SocketClient getWaitCustomer(String tenantCode, String clientId, RedisService redisService) {
        return (SocketClient) redisService.getMapField(RedisConstant.SCOKET_CLIENT_WAIT_C + tenantCode, clientId);
    }

    /**
     * 移除等待的用户
     *
     * @param tenantCode
     * @param customerId
     * @param redisService
     * @return
     */
    public static void removeWaitCustomer(String tenantCode, String customerId, RedisService redisService) {
        redisService.removeMapField(RedisConstant.SCOKET_CLIENT_WAIT_C + tenantCode, customerId);
    }

    /**
     * 保存聊天记录
     */
    public static void saveChatRecord(MsgInfo data, String tenantCode, String roomId, ChatRecordService chatRecordService) {
        ChatRecord chatRecord = new ChatRecord(data, roomId, tenantCode);
        chatRecordService.save(chatRecord);

    }

    public static void saveChatRecord(MsgInfo data, String tenantCode, String roomId, ChatRecordMGService chatRecordMGService) {
        ChatRecord chatRecord = new ChatRecord(data, roomId, tenantCode);
        chatRecordMGService.save(chatRecord);
    }

    /**
     * 分配客服
     */
    public static boolean allotCS(SocketIOClient client, String customerId, String tenantCode,
                                  RedisService redisService, SocketIOServer socketIOServer, RoomClientService roomClientService,
                                  SocketRoomService socketRoomService, SocketClientService socketClientService,
                                  ClientServiceRecordService clientServiceRecordService) {
        //是否分配成功
        Boolean isAllot = Boolean.FALSE;
        //是否匹配到上次联系的客服
        Boolean allotLastClient = Boolean.FALSE;
        SocketClient customerRedisSocketClient = SocketUtil.getRedisSocketClient(SocketClientType.客户, tenantCode, customerId, redisService);
        if (customerRedisSocketClient == null) {
            //客户已断开
            return Boolean.FALSE;
        }
        //0.查找上次客服是否可用
        String lastConnectClientId = customerRedisSocketClient.getLastConnectClientId();
        if (StringUtils.isNotBlank(lastConnectClientId)) {
            SocketClient lastConnectSocketClient = SocketUtil.getRedisSocketClient(SocketClientType.客服, tenantCode, lastConnectClientId, redisService);
            if(lastConnectSocketClient != null){
                isAllot = CSisAvailable(lastConnectSocketClient, customerRedisSocketClient, client, socketIOServer,
                        redisService, socketClientService, roomClientService, clientServiceRecordService,
                        socketRoomService,customerId);
                if(isAllot){
                    allotLastClient = true;
                }
            }
        }
        if (!allotLastClient) {
            //1.查找客服
            Map<String, SocketClient> csRedisClients = redisService.getMap(RedisConstant.SCOKET_CLIENT_CS + tenantCode);

            //2.检查客服是否可用
            if (csRedisClients != null && !csRedisClients.isEmpty()) {
                List<SocketClient> values = Lists.newArrayList();
                values.addAll(csRedisClients.values());
                // 客服的连接客户数 小到大排序
                Collections.sort(values, ((o1, o2) -> (o1.getConnectCustomerNum()==null?0:o1.getConnectCustomerNum())-(o2.getConnectCustomerNum()==null?0:o2.getConnectCustomerNum())));

                // 分配客服
                for (SocketClient targetRedisClient : values) {
                    isAllot = CSisAvailable(targetRedisClient, customerRedisSocketClient, client, socketIOServer,
                            redisService, socketClientService, roomClientService, clientServiceRecordService,
                            socketRoomService,customerId);
                    if(isAllot){
                        break;
                    }
                }
            } else {
                isAllot = Boolean.FALSE;
            }
        }
        if (!isAllot) {
            //12.发送无可用客服事件
            SocketEventUtil.notExistAvailableCS(client, customerRedisSocketClient.getUserName(), customerId);
            //13.进入等待队列
            saveWaitClient2Redis(customerRedisSocketClient, tenantCode, customerId, redisService);
        } else {
            //14.删除等待中的客户
            SocketUtil.removeWaitCustomer(tenantCode, customerId, redisService);
        }
        return isAllot;
    }

    /**
     * 客服是否可用并分配成功
     * @param targetRedisClient 客服redis客户端
     * @param customerRedisSocketClient 客户redis客户端
     * @param client 客户的socketIO客户端
     * @param socketIOServer
     * @param redisService
     * @param socketClientService
     * @param roomClientService
     * @param clientServiceRecordService
     * @param socketRoomService
     * @return
     */
    public static boolean CSisAvailable(SocketClient targetRedisClient, SocketClient customerRedisSocketClient, SocketIOClient client,
                                        SocketIOServer socketIOServer, RedisService redisService, SocketClientService socketClientService,
                                        RoomClientService roomClientService, ClientServiceRecordService clientServiceRecordService,
                                        SocketRoomService socketRoomService,String customerId){
        String tenantCode = customerRedisSocketClient.getTenantCode();
        String customerClientId = customerRedisSocketClient.getClientId();

        Boolean connected = targetRedisClient.getConnected();
        ServiceStatus serviceStatus = targetRedisClient.getServiceStatus();

        //更新客服连接的客户数量
        Integer connectCustomerNum = targetRedisClient.getConnectCustomerNum();
        if(connectCustomerNum == null){
            connectCustomerNum = 0;
        }
        targetRedisClient.setConnectCustomerNum(++connectCustomerNum);
        //1.分配成功
       if (connected && Objects.equals(serviceStatus, ServiceStatus.正常) && targetRedisClient.getConnectCustomerNum() <= ChatConstant.CS_MAX_CONNECT_CUSTOMER_NUM) {

            SocketIOClient targetSocketIOClient = SocketUtil.getSocketIOClient(targetRedisClient, socketIOServer);
            if (targetSocketIOClient != null) {

                //2.保存客户最近一次联系的客服
                customerRedisSocketClient.setLastConnectClientId(targetRedisClient.getUserId());
                socketClientService.update(customerRedisSocketClient);
                SocketUtil.saveClient2Redis(customerRedisSocketClient, null, SocketClientType.客户, tenantCode, customerRedisSocketClient.getUserId(), redisService, null);
                Date now = new Date();

                //判断是否为客户刷新，刷新改回客服连接数
                Set<String> clientRooms = SocketUtil.getClientRooms(targetSocketIOClient);
                if(clientRooms.contains(customerClientId)){
                    targetRedisClient.setConnectCustomerNum(--connectCustomerNum);
                }
                //保存客服连接的客户数量
                SocketUtil.saveClient2Redis(targetRedisClient, null, SocketClientType.客服, tenantCode, targetRedisClient.getUserId(), redisService, null);
                socketClientService.update(targetRedisClient);

                //5.客户和客服进入房间
                targetSocketIOClient.joinRoom(customerClientId);
                client.joinRoom(customerClientId);

                //6.保存客户端和房间的关系
                RoomClient roomClient = roomClientService.findByRoomId(customerClientId, targetRedisClient.getUserId());
                if (roomClient == null) {
                    roomClient = new RoomClient();
                    roomClient.setClientId(targetRedisClient.getUserId());
                    roomClient.setJoinTime(now);
                    roomClient.setRoomId(customerClientId);
                    roomClient.setStatus(SocketRoomStatus.正常);
                    roomClient.setCreatedBy(ChatConstant.SYS_OPRATION_NAME);
                    roomClient.setCreatedDate(now);
                    roomClientService.save(roomClient);
                } else {
                    roomClient.setClientId(targetRedisClient.getUserId());
                    roomClient.setJoinTime(now);
                    roomClient.setRoomId(customerClientId);
                    roomClient.setStatus(SocketRoomStatus.正常);
                    roomClient.setLastModifiedDate(now);
                    roomClient.setLastModifiedBy(ChatConstant.SYS_OPRATION_NAME);
                    roomClientService.update(roomClient);
                }
                //7.保存服务记录
                ClientServiceRecord clientServiceRecord = new ClientServiceRecord();
                clientServiceRecord.setCreatedBy(ChatConstant.SYS_OPRATION_NAME);
                clientServiceRecord.setCreatedDate(now);
                clientServiceRecord.setCsClientId(targetRedisClient.getClientId());
                clientServiceRecord.setCustomerClientId(customerClientId);
                clientServiceRecord.setStartTime(now);
                clientServiceRecordService.save(clientServiceRecord);
                //8.查找房间
                boolean socketRoomIsNew = false;
                SocketRoom socketRoom = socketRoomService.findSocketRoomByRoomId(customerClientId);
                if (socketRoom == null) {
                    socketRoomIsNew = true;
                    socketRoom = new SocketRoom();
                    socketRoom.setCustomerName(customerRedisSocketClient.getUserName());
                    socketRoom.setCreatedBy(ChatConstant.SYS_OPRATION_NAME);
                    socketRoom.setCreatedDate(now);
                    socketRoom.setRoomId(customerClientId);
                    socketRoom.setTenantCode(tenantCode);
                    socketRoom.setType(SocketClientType.客户);
                }
                socketRoom.setCustomerId(customerId);
                socketRoom.setLastModifiedDate(now);
                socketRoom.setLastModifiedBy(ChatConstant.SYS_OPRATION_NAME);
                socketRoom.setStatus(SocketRoomStatus.正常);
                socketRoom.setJoinTime(now);
                //9.保存房间数据
                redisService.addMap(RedisConstant.SCOKET_ROOM_C + tenantCode, customerRedisSocketClient.getUserId(), socketRoom);
                if (socketRoomIsNew) {
                    socketRoomService.save(socketRoom);
                } else {
                    socketRoomService.update(socketRoom);
                }
                //10.发送客服进入房间事件
                BroadcastOperations roomOperations = socketIOServer.getRoomOperations(customerClientId);
                SocketEventUtil.csIntoRoom(roomOperations, targetRedisClient.getClientId(), targetRedisClient.getUserName());
                //11.发送房间信息给客服
                List<SocketClient> socketClients = socketClientService.findByRoomId(customerClientId);
                Map<String, Object> roomInfo = Maps.newHashMap();
                roomInfo.put("room", socketRoom);
                roomInfo.put("customer", customerRedisSocketClient);
                roomInfo.put("cs", socketClients);
                SocketEventUtil.tellCSRoomInfo(targetSocketIOClient, customerClientId, targetRedisClient.getUserName(), roomInfo);
                return true;
            }
        }
        return false;
    }
}

